package shlib

import (
	"encoding/binary"
	"fmt"
	"io"
	"os"
	"os/exec"
	"path/filepath"
	"reflect"
	"runtime"
	"strconv"
	"strings"
	"time"
	"unsafe"
)

/*
 * 返回数字字符串的整数值。
 */
func AtoiNe(a string) int {
	if i, err := strconv.Atoi(a); err != nil {
		panic(err)
	} else {
		return i
	}
}

/**
 * 将字节数组强制转换为字符串。
 */
func Bytes2String(data []byte) string {
	return *(*string)(unsafe.Pointer(&data))
}

/**
 * 返回函数调用栈中向上 skip 层的函数名称。
 * skip == 0 时，返回 "shlib.Caller"
 * skip == 1 时，返回调用本函数的函数名称。
 */
func Caller(skip int) string {
	pc, _, _, ok := runtime.Caller(skip)
	if !ok {
		return ""
	}

	fn := runtime.FuncForPC(pc).Name()
	i := strings.LastIndexByte(fn, '/')
	if i >= 0 {
		fn = fn[i+1:]
	}
	i = strings.LastIndexByte(fn, '-')
	if i >= 0 {
		fn = fn[i+1:]
	}

	return fn
}

func FileLine(skip int) (string, int) {
	_, file, line, _ := runtime.Caller(skip)
	i := strings.LastIndexByte(file, '/')

	return file[i+1:], line
}

/*
 * 将一个 64 位整数，当做一个 []byte 来看待。
 */
func Int2Bytes(data *int) []byte {
	slhead := reflect.SliceHeader{
		Data: uintptr(unsafe.Pointer(data)),
		Len:  int(unsafe.Sizeof(int(1))),
		Cap:  int(unsafe.Sizeof(int(1))),
	}
	return *(*[]byte)(unsafe.Pointer(&slhead))
}

/**
 * 以 HH:MM:SS 的格式返回当前时间。
 */
func NowString() string {
	now := time.Now()
	return fmt.Sprintf("%02d:%02d:%02d", now.Hour(), now.Minute(), now.Second())
}

/*
 * 返回本程序可执行文件的绝对路径。
 */
func ProgramPath() string {
	f, err := exec.LookPath(os.Args[0])
	if err != nil {
		return ""
	}
	p, err := filepath.Abs(f)
	if err != nil {
		return ""
	}
	return p
}

/*
 * 获取本程序文件所在的绝对目录。返回字符中保留最后的 "/"。
 */
func ProgramDir() string {
	p := ProgramPath()
	i := strings.LastIndex(p, "/")
	if i < 0 {
		return ""
	}
	return p[:i+1]
}

func ReadUint32(r io.Reader) (uint32, error) {
	var buf [4]byte

	b := buf[:]
	if _, err := io.ReadFull(r, b); err != nil {
		return 0, err
	}

	return binary.BigEndian.Uint32(b), nil
}

/**
 * 向 io.Writter 中写入指定数据，全部写完为止。
 */
func WriteFull(w io.Writer, data []byte) error {
	dataLen := len(data)
	nWritten := 0

	for nWritten < dataLen {
		if n, err := w.Write(data[nWritten:]); err != nil {
			return err
		} else {
			nWritten += n
		}
	}

	return nil
}
